package tr.com.netgis;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import tr.com.netgis.db.NGDbHelper;
import tr.com.netgis.pojo.NGLocation;
import tr.com.netgis.util.Constants;
import tr.com.netgis.util.RotatingNGLocation;
import tr.com.netgis.util.Status;
import tr.com.netgis.util.Utility;
import tr.com.netgis.web.SetLocationRequest;
import tr.com.netgis.web.SetLocationResponse;
import tr.com.netgis.web.WebServisCaller;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;

/**
 * 
 * @author NetGis
 *
 */
public class SetLocationService extends Service {

	private static RotatingNGLocation LAST_N_LOCATIONS;
	
	public static RotatingNGLocation getLastNLocations (){
		return LAST_N_LOCATIONS;
	}
	
	private final Object latestSearchResultLock = new Object();
	private SetLocationResult latestResult = new SetLocationResult();
	private List<SetLocationListener> listeners = new ArrayList<SetLocationListener>();
	private static final String TAG = SetLocationService.class.getSimpleName();
	private static final String TAG_LS = "LOCATION SERVICE";
	private Timer timer;
	
	public LocationManager locationManager;
	public String locationManagerProvider = "unknown";
	public int locationManagerProviderStatus = -1;
	
	/**
	 * last changed location
	 */
	private Location lastLocation;

	private LocationListener locationListener;
	
	/**
	 * onLocationChange'de set edilir
	 * @return
	 */
	public Location getLastLocation (){
		return this.lastLocation;
	}
	
	public void settLastLocation (Location lastLocation){
		this.lastLocation = lastLocation;
	}
	
	public String getLocationManagerProvider (){
		return this.locationManagerProvider;
	}
	
	public String getLocationManageProviderStatus() {
		switch (this.locationManagerProviderStatus) {
		case LocationProvider.AVAILABLE:
			return "AVAILABLE";
		case LocationProvider.OUT_OF_SERVICE:
			return "OUT_OF_SERVICE";
		case LocationProvider.TEMPORARILY_UNAVAILABLE:
			return "TEMPORARILY_UNAVAILABLE";
		default:
			return "BILINMEYEN";
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		Log.d(TAG, "Boundinnnnggg " + intent);
		if (SetLocationService.class.getName().equals(intent.getAction())) {
			Log.d(TAG, "Bound by intent " + intent);
			return apiEndpoint;
		} else {
			return null;
		}
	}

	@Override
	public void onCreate() {
		super.onCreate();
        nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
		Log.i(TAG, "Service creating");
		
		LAST_N_LOCATIONS = new RotatingNGLocation(Constants.LAST_N_LOCATIONS);
		
		buildAlertMessageNoGps();
		
		locationListener = new NGLocationListener(this);
		refresh(true);
        showNotification("Mobiz", "Mobil İzleme Başladı");		
        inititializeDB();
	}
	

	@Override
	public void onDestroy() {
		super.onDestroy();
		
		//Uygulama Kapatıldı durumun gönder
		
		
		NGLocation lastNGLocation = LAST_N_LOCATIONS.getLastInstertedElement();
		if (lastNGLocation != null) {
			try {
				lastNGLocation.setDate(new Date().getTime());
				lastNGLocation.calculateSpeedAndStatus(LAST_N_LOCATIONS);
				lastNGLocation.setStatus(Status.UYGULAMA_KAPANDI);
				SharedPrefs prefs = SharedPrefs.getInstance();
				SetLocationRequest request = new SetLocationRequest();
				request.setLocation(lastNGLocation);
				request.setServerUrl(prefs.getServerUrl());		
				SetLocationResponse response = WebServisCaller.callWebService(request);
				FileLogger.log(this, response);	
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		Log.i(TAG, "Service destroying");
		timer.cancel();
		timer = null;
		locationManager.removeUpdates(locationListener);
		nm.cancel(R.string.service_notif);
		mHelper.close();
	}

    public static final int MSG_MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1;
    public static final int MSG_MINIMUM_TIME_BETWEEN_UPDATES = 2;
	
	/**
	 * 
	 * @author NetGis
	 *
	 */
	class UpdateTask extends TimerTask {
		
		private SetLocationService setLocationService;
		
		public UpdateTask(SetLocationService service) {
			super();
			this.setLocationService = service;
		}

		@Override
		public void run() {
			Log.i(TAG, "Send location service working");
			try {
				SetLocationResult newSearchResult = new SetLocationResult();
				newSearchResult.setmData(Utility.createSimpleDateFormat().format(new Date()) + "\n" + setLocationService.getLocationManagerProvider() + ": " + setLocationService.getLocationManageProviderStatus());
				//newSearchResult.add(lastLocation.toString());
				//Location location = locationManager.getLastKnownLocation(locationManagerProvider);				
				NGLocation lastNGLocation = LAST_N_LOCATIONS.getLastInstertedElement();
				/*
				NGLocation lastNGLocation = null;
				if (setLocationService.getLastLocation() != null)
					lastNGLocation = new NGLocation(setLocationService.getLastLocation());
				*/
				
				if (lastNGLocation == null) {
					Log.d(TAG, "Last NGLocation null");
				} else if (!lastNGLocation.isSent()) {
					Log.d(TAG, "Last Element: " + lastNGLocation);
					//Log.i("HIIZZ", Double.toString(loc.getSpeed()));
					lastNGLocation.setDate(new Date().getTime());
					lastNGLocation.calculateSpeedAndStatus(LAST_N_LOCATIONS);
					newSearchResult.add(lastNGLocation);
					newSearchResult.setAbout(setLocationService.about.toString());
					
					SharedPrefs prefs = SharedPrefs.getInstance();
					
					if (lastNGLocation.needToBeSent() && prefs.isSendToServer()) {
						SetLocationRequest request = new SetLocationRequest();
						request.setLocation(lastNGLocation);
						request.setServerUrl(prefs.getServerUrl());					
						SetLocationResponse response = WebServisCaller.callWebService(request);
						FileLogger.log(setLocationService, response);
						Log.i(TAG_LS, response.toString());
						if (response.hasException()) {
							mHelper.insert(lastNGLocation);
						} else {
							lastNGLocation.setSent((byte)1);
						}
					} else {
						Log.d(TAG, "Last Location already sent: " + lastNGLocation);						
					}
				} else {
					Log.d(TAG, "Last Element (sent): " + lastNGLocation);
				}
				
				synchronized (latestSearchResultLock) {
					latestResult = newSearchResult;
				}
			
				synchronized (listeners) {
					for (SetLocationListener listener : listeners) {
						try {
							listener.handleLocationsUpdated();
						} catch (RemoteException e) {
							Log.w(TAG, "Failed to notify listener " + listener, e);
						}
					}
				}
			} catch (Throwable t) { /* you should always ultimately catch 
									   all exceptions in timer tasks, or 
									   they will be sunk */
				Log.e(TAG, "Failed to retrieve", t);
			}						
		}
		
		
	}
	
	private SetLocationApi.Stub apiEndpoint = new SetLocationApi.Stub() {

		@Override
		public SetLocationResult getResult() throws RemoteException {
			return latestResult;
		}

		@Override
		public void addListener(SetLocationListener listener)
				throws RemoteException {
			synchronized (listeners) {
				listeners.add(listener);
			}
		}

		@Override
		public void removeListener(SetLocationListener listener)
				throws RemoteException {
			synchronized (listeners) {
				listeners.remove(listener);
			}
		}

		@Override
		public void sendCommand(int command) throws RemoteException {
			Log.i(TAG, "Command: " + command);
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Looper.prepare();
					refresh(false);
					Looper.loop();
					Looper.getMainLooper().quit();
				}
			}).start();
		}
	};

	class NGLocationListener implements LocationListener {
		
		SetLocationService setLocationService;
		
		public NGLocationListener(SetLocationService setLocationService) {
			this.setLocationService = setLocationService;
		}
		
		@Override
		public void onLocationChanged(Location location) {
			Log.i("CHANGED-" + TAG, location.toString());
			NGLocation ngLocation = new NGLocation(location);
			ngLocation.calculateSpeedAndStatus(LAST_N_LOCATIONS);
			setLocationService.settLastLocation(location);
			LAST_N_LOCATIONS.insertElement(ngLocation);
			Log.e("CHANGED-" + TAG_LS, ngLocation.toString());
		}		
		
		@Override
		public void onProviderEnabled(String provider) {
			Log.i(TAG_LS, "Enabled new provider " + provider);
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Looper.prepare();
					refresh(false);
					Looper.loop();
					Looper.getMainLooper().quit();
				}
			}).start();
		}

		@Override
		public void onProviderDisabled(String provider) {
			Log.i(TAG_LS, "Disabled provider " + provider);
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Looper.prepare();
					refresh(false);
					Looper.loop();
					Looper.getMainLooper().quit();
				}
			}).start();
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			Log.i(TAG_LS, "Provider status changed : " + status);
			setLocationService.locationManagerProviderStatus = status;
		}		
	}
	
	private StringBuffer about = new StringBuffer();
	
	private void initializeLocationManager () {
		
		// Get the location manager
		this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		// Define the criteria how to select the locatioin provider -> use
		// default
		Criteria criteria = new Criteria(); 
		
		criteria.setAccuracy(Criteria.ACCURACY_FINE); 
		criteria.setAltitudeRequired(false); 
		criteria.setBearingRequired(false); 
		criteria.setCostAllowed(true); 
		criteria.setPowerRequirement(Criteria.POWER_HIGH); 
		
		this.locationManagerProvider = this.locationManager.getBestProvider(criteria, true);
		FileLogger.log("Best Provider: " + this.locationManagerProvider);
		FileLogger.log("Use GPS: " + this.useGPS());
		
		if (this.useGPS() && this.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
			this.locationManagerProvider = LocationManager.GPS_PROVIDER;
		} else if (this.locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
			this.locationManagerProvider = LocationManager.NETWORK_PROVIDER;			
		}  else if (this.locationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) {
			this.locationManagerProvider = LocationManager.PASSIVE_PROVIDER;
		}
		
		FileLogger.log("Selected Provider: " + this.locationManagerProvider);

		
		SharedPrefs prefs = SharedPrefs.getInstance();
		
		long minTime = prefs.getMinTime();
		float minDistance = prefs.getMinDistance();
		
		this.locationManager.requestLocationUpdates(this.locationManagerProvider, minTime, minDistance, locationListener);		
		//this.lastLocation = locationManager.getLastKnownLocation(this.locationManagerProvider);

		this.about = new StringBuffer();
		this.about.append("\nProvider: " + this.locationManagerProvider);
		this.about.append("\nLocation Manager initialized");
	}

	@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Received start id " + startId + ": " + intent);
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }
	
	private boolean useGPS () {
		SharedPrefs prefs = SharedPrefs.getInstance();
		boolean useGPS = prefs.useGPS();
	    Log.d(TAG, "*** Use GPS: " + useGPS);				
	    return useGPS;
	}
	
	NotificationManager nm;
	
	private void showNotification(String message, String messageTicket) {
        Notification notification = new Notification(R.drawable.ic_help, messageTicket, System.currentTimeMillis());
        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, messageTicket.hashCode(), null, 0);
        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, "Mobil İzleme", message, contentIntent);
        notification.flags |= Notification.FLAG_AUTO_CANCEL | Notification.FLAG_INSISTENT;
        // Send the notification.
        // We use a layout id because it is a unique number.  We use it later to cancel.
        nm.notify(R.string.service_notif, notification);
    }
	
	private void initializeTimer (){
		SharedPrefs prefs = SharedPrefs.getInstance();
		long period = prefs.getSendFrequency();
		Log.i(TAG, "Period: " + Long.toString(period));
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
		timer = new Timer("SetLocationTimer " + UUID.randomUUID().toString());
		timer.schedule(new UpdateTask(this), 1000L, period);		
	}
	
	private void refresh (boolean initialize){
		initializeTimer();
		if (!initialize)
			this.locationManager.removeUpdates(this.locationListener);
		initializeLocationManager();				
	}
	
	NGDbHelper mHelper;
	
	private void inititializeDB (){
		mHelper = new NGDbHelper(this);	
	}
	
	private void buildAlertMessageNoGps() {
		if (SharedPrefs.getInstance().useGPS() && !Utility.isGPSEnabled(getApplicationContext())) {
			showNotification("Mobiz", "GPS aktif değil, aktifleştirmeniz konum doğruluğunu arttırır?");
		}
		
		/*
		if (NetMobileLocationSharedPrefs.getInstance().useGPS() && !Utility.isGPSEnabled(getApplicationContext())) {
		    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
		    String message = "GPS aktif değil, aktifleştirmek ister misiniz?";
		    builder.setMessage(message).setCancelable(false).setPositiveButton("Evet",
	            new DialogInterface.OnClickListener() {
	                public void onClick(
	                        final DialogInterface dialog,
	                        final int id) {
	                    Intent intent = new Intent(
	                            Settings.ACTION_LOCATION_SOURCE_SETTINGS);
	                    startActivity(intent);
	                }
	            }).setNegativeButton("Hayır",
	            new DialogInterface.OnClickListener() {
	                public void onClick(final DialogInterface dialog,
	                        final int id) {
	                    dialog.cancel();
	                }
	            });
		    final AlertDialog alert = builder.create();
		    alert.show();			
		}
		*/
	}	
}
